﻿// ....................................................................................................................
//	Author: Copyright (C) 2012 Ing. Marek Hubal
//	Email: hubal@acasoftware.org
// ....................................................................................................................

#pragma warning disable 0618

using System;

namespace core
{
    /// <summary>Trieda ICloudStorageDef</summary>
	public class ICloudStorageDef
	{
        public const int TYPE_UNKNOWN = 0;
        public const int TYPE_FTP = 1;

        public static int IDCounter = 1; // Pocitadlo ID
        public int ID; // ID objektu
        public int Type;

        /// <summary>Konstruktor</summary>
        public ICloudStorageDef()
        {
            ID = IDCounter++; // Inicializacia
            Type = TYPE_UNKNOWN;
        }

        /// <summary>Editacia obsahu pomocou formulara</summary>
        /// <returns>true / false</returns>
        public virtual bool EditWithForm()
        {
            return false;
        }

        /// <summary>Property: PasswordCacheID</summary>
        public virtual string PasswordCacheID
        {
            get
            {
                return "";
            }
        }
        /// <summary>Property: Name</summary>
        public virtual string Name
        {
            get
            {
                return "";
            }
        }
        /// <summary>Property: HotPathConnectionProvider</summary>
        public virtual string HotPathConnectionProvider
        {
            get
            {
                return "";
            }
        }

        /// <summary>Serializacia obsahu</summary>
        /// <param name="pStream">stream</param>
        public virtual void Serialize(System.IO.BinaryWriter pStream)
        {
        }
    }

    /// <summary>Trieda IFTPStorageDef</summary>
    public class IFTPStorageDef : ICloudStorageDef
    {
        public string Conn_Name;
        public string Conn_Caption;
        public string Conn_ServerName;
        public string Conn_LoginName;
        public string Conn_LoginPwd;
        public string Conn_RemoteFolder;
        public bool Conn_PassiveMode;

        /// <summary>Konstruktor</summary>
        public IFTPStorageDef()
            : base()
        {
            Conn_Name = "";
            Conn_Caption = "";
            Conn_LoginName = "";
            Conn_LoginPwd = "";
            Conn_PassiveMode = false;
            Conn_RemoteFolder = "";
            Conn_ServerName = "";
            Type = TYPE_FTP;
        }

        /// <summary>Skopirovanie obsahu objektu</summary>
        /// <param name="pValue">objekt pre skopirovanie</param>
        public void Assign(IFTPStorageDef pValue)
        {
            ID = pValue.ID;
            Type = pValue.Type;
            Conn_Name = pValue.Conn_Name;
            Conn_Caption = pValue.Conn_Caption;
            Conn_ServerName = pValue.Conn_ServerName;
            Conn_LoginName = pValue.Conn_LoginName;
            Conn_LoginPwd = pValue.Conn_LoginPwd;
            Conn_RemoteFolder = pValue.Conn_RemoteFolder;
            Conn_PassiveMode = pValue.Conn_PassiveMode;
        }
        /// <summary>Editacia obsahu pomocou formulara</summary>
        /// <returns>true / false</returns>
        public override bool EditWithForm()
        {
            using (IFileExplorerForm_FTPStorageDef_Edit F = new IFileExplorerForm_FTPStorageDef_Edit()) // Vytvorim okno
            {
                F.Value.Assign(this); // Skopirujem aktualny obsah
                bool B = F.ShowDialog() == System.Windows.Forms.DialogResult.OK; // Zobrazim
                if (B) Assign(F.Value); // Ziskam zmenene hodnoty
                return B;
            }
        }

        /// <summary>Property: PasswordCacheID</summary>
        public override string PasswordCacheID
        {
            get
            {
                return string.Format("CLOUD_PWD_{0}", ID).ToLower();
            }
        }
        /// <summary>Property: Name</summary>
        public override string Name
        {
            get
            {
                return Conn_Name;
            }
        }
        /// <summary>Property: HotPathConnectionProvider</summary>
        public override string HotPathConnectionProvider
        {
            get 
            {
                return Conn_ServerName.GetHashCode().ToString() + Conn_LoginName.GetHashCode().ToString() + Conn_LoginPwd.GetHashCode().ToString();
            }
        }

        /// <summary>Serializacia obsahu</summary>
        /// <param name="pStream">stream</param>
        public override void Serialize(System.IO.BinaryWriter pStream)
        {
            pStream.Write(Conn_Name);
            pStream.Write(Conn_Caption);
            pStream.Write(Conn_ServerName);
            pStream.Write(Conn_LoginName);
            pStream.Write(Conn_LoginPwd);
            pStream.Write(Conn_RemoteFolder);
            pStream.Write(Conn_PassiveMode);
        }
        /// <summary>Deserializacia obsahu</summary>
        /// <param name="pStream">stream</param>
        public void Deserialize(System.IO.BinaryReader pStream)
        {
            Conn_Name = pStream.ReadString();
            Conn_Caption = pStream.ReadString();
            Conn_ServerName = pStream.ReadString();
            Conn_LoginName = pStream.ReadString();
            Conn_LoginPwd = pStream.ReadString();
            Conn_RemoteFolder = pStream.ReadString();
            Conn_PassiveMode = pStream.ReadBoolean();
        }
    }

    /// <summary>Trieda IFileExplorerForm_FTPStorageDef_Edit</summary>
    public class IFileExplorerForm_FTPStorageDef_Edit : IForm
    {
        public IFTPStorageDef Value; // Objekt pre editaciu

        protected IGroupBox FGrp; // Controls
        protected IButton FBtn_OK;
        protected IButton FBtn_Cancel;

        protected ITextBox FText_Name;
        protected ITextBox FText_Caption;
        protected ITextBox FText_ServerName;
        protected ITextBox FText_LoginName;
        protected ITextBox FText_LoginPwd;
        protected ITextBox FText_RemoteFolder;
        protected ICheckBox FCheck_Pasive;
        protected ICheckBox FCheck_AnonymousLogin;

        protected IGroupBox FGrp1, FGrp2, FGrp3;
        protected ILabel FLabel1, FLabel2, FLabel3, FLabel4, FLabel5, FLabel6;

        /// <summary>Konstruktor</summary>
        public IFileExplorerForm_FTPStorageDef_Edit()
        {
            Value = new IFTPStorageDef(); // Inicializacia
        }

        /// <summary>Metoda: OnCreateForm</summary>
        /// <returns>true / false</returns>
        protected override bool OnCreateForm()
        {
            if (!base.OnCreateForm()) return false; // Volam predchodcu

            // Inicializacia
            MinimizeBox = false; MaximizeBox = false; FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; ShowIcon = true; ShowInTaskbar = false; StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;

            SetBounds(0, 0, 488, 540); System.Drawing.Size CS = ClientSize;
            Text = lang.FORM_EDIT_CHANGE_FTP_CONNECTION_TEXT_1;

            // Vytvorim groupbox
            FGrp = new IGroupBox(); IControl.PlaceControl_Inside(FGrp, this, 10, 10, 10, 50);

            FGrp1 = new IGroupBox(); IControl.PlaceControl_Inside_LeftTop_LeftTop(FGrp1, FGrp, 10, 10, FGrp.ClientSize.Width - 20, 120);
            FLabel1 = new ILabel(lang.FORM_EDIT_CHANGE_FTP_CONNECTION_TEXT_4); FText_Name = new ITextBox();
            FLabel2 = new ILabel(lang.FORM_EDIT_CHANGE_FTP_CONNECTION_TEXT_5); FText_Caption = new ITextBox();
            FGrp1.Controls.AddRange(new System.Windows.Forms.Control[] { FLabel1, FText_Name, FLabel2, FText_Caption });
            IControl.PlaceControl_Inside_LeftTop_LeftTop(FLabel1, FGrp1, 10, 20);
            IControl.PlaceControl_Outside_LeftTop_LeftBottom(FText_Name, FLabel1, 0, 6, FGrp1.ClientSize.Width - 20);
            IControl.PlaceControl_Outside_LeftTop_LeftBottom(FLabel2, FText_Name, 0, 10);
            IControl.PlaceControl_Outside_LeftTop_LeftBottom(FText_Caption, FLabel2, 0, 6, FGrp1.ClientSize.Width - 20);

            FGrp2 = new IGroupBox(); IControl.PlaceControl_Outside_LeftTop_LeftBottom(FGrp2, FGrp1, 0, 6, FGrp.ClientSize.Width - 20, 150);
            FLabel3 = new ILabel(lang.FORM_EDIT_CHANGE_FTP_CONNECTION_TEXT_6); FText_ServerName = new ITextBox();
            FLabel4 = new ILabel(lang.FORM_EDIT_CHANGE_FTP_CONNECTION_TEXT_7); FText_RemoteFolder = new ITextBox();
            FCheck_Pasive = new ICheckBox(lang.FORM_EDIT_CHANGE_FTP_CONNECTION_TEXT_8);
            FGrp2.Controls.AddRange(new System.Windows.Forms.Control[] { FLabel3, FText_ServerName, FLabel4, FText_RemoteFolder, FCheck_Pasive });
            IControl.PlaceControl_Inside_LeftTop_LeftTop(FLabel3, FGrp2, 10, 20);
            IControl.PlaceControl_Outside_LeftTop_LeftBottom(FText_ServerName, FLabel3, 0, 6, FGrp2.ClientSize.Width - 20);
            IControl.PlaceControl_Outside_LeftTop_LeftBottom(FLabel4, FText_ServerName, 0, 10);
            IControl.PlaceControl_Outside_LeftTop_LeftBottom(FText_RemoteFolder, FLabel4, 0, 6, FGrp2.ClientSize.Width - 20);
            IControl.PlaceControl_Outside_LeftTop_LeftBottom(FCheck_Pasive, FText_RemoteFolder, 0, 10);

            FGrp3 = new IGroupBox(); IControl.PlaceControl_Outside_LeftTop_LeftBottom(FGrp3, FGrp2, 0, 6, FGrp.ClientSize.Width - 20, 150);
            FLabel5 = new ILabel(lang.FORM_EDIT_CHANGE_FTP_CONNECTION_TEXT_9); FText_LoginName = new ITextBox();
            FLabel6 = new ILabel(lang.FORM_EDIT_CHANGE_FTP_CONNECTION_TEXT_10); FText_LoginPwd = new ITextBox(); FText_LoginPwd.PasswordChar = '*';
            FCheck_AnonymousLogin = new ICheckBox(lang.FORM_EDIT_CHANGE_FTP_CONNECTION_TEXT_11);
            FGrp3.Controls.AddRange(new System.Windows.Forms.Control[] { FLabel5, FText_LoginName, FLabel6, FText_LoginPwd, FCheck_AnonymousLogin });
            IControl.PlaceControl_Inside_LeftTop_LeftTop(FLabel5, FGrp3, 10, 20);
            IControl.PlaceControl_Outside_LeftTop_LeftBottom(FText_LoginName, FLabel5, 0, 6, FGrp3.ClientSize.Width - 20);
            IControl.PlaceControl_Outside_LeftTop_LeftBottom(FLabel6, FText_LoginName, 0, 10);
            IControl.PlaceControl_Outside_LeftTop_LeftBottom(FText_LoginPwd, FLabel6, 0, 6, FGrp3.ClientSize.Width - 20);
            IControl.PlaceControl_Outside_LeftTop_LeftBottom(FCheck_AnonymousLogin, FText_LoginPwd, 0, 10);

            // Vytvorim tlacidla
            FBtn_Cancel = new IButton(lang.FORM_EDIT_CHANGE_FTP_CONNECTION_TEXT_3); IControl.PlaceControl_Outside_TopRight_RightBottom(FBtn_Cancel, FGrp, 0, 10); FBtn_Cancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
            FBtn_OK = new IButton(lang.FORM_EDIT_CHANGE_FTP_CONNECTION_TEXT_2, Event_BtnOKClick); IControl.PlaceControl_Outside_TopRight_LeftTop(FBtn_OK, FBtn_Cancel, 0, 10);

            FGrp.Controls.AddRange(new System.Windows.Forms.Control[] { FGrp1, FGrp2, FGrp3 });
            Controls.AddRange(new System.Windows.Forms.Control[] { FGrp, FBtn_OK, FBtn_Cancel }); // Zaradim do controls

            AcceptButton = FBtn_OK; CancelButton = FBtn_Cancel; FBtn_OK.Focus();

            FCheck_AnonymousLogin.CheckedChanged += (s, e) => // Event CheckedChanged
                {
                    FText_LoginName.Enabled = !FCheck_AnonymousLogin.Checked;
                    FText_LoginPwd.Enabled = !FCheck_AnonymousLogin.Checked;
                };

            FillForm(Value); // Vyplnim zaciatocnou hodnotou
            return true; // Vsetko OK
        }

        /// <summary>Vyplnenie formulara</summary>
        /// <param name="pValue">objekt s hodnotami</param>
        protected void FillForm(IFTPStorageDef pValue)
        {
            FText_Name.Text = pValue.Conn_Name;
            FText_Caption.Text = pValue.Conn_Caption;
            FText_ServerName.Text = pValue.Conn_ServerName;
            FText_RemoteFolder.Text = pValue.Conn_RemoteFolder;
            FCheck_Pasive.Checked = pValue.Conn_PassiveMode;
            FText_LoginName.Text = pValue.Conn_LoginName;
            FText_LoginPwd.Text = pValue.Conn_LoginPwd;
            FCheck_AnonymousLogin.Checked = FText_LoginName.Text.ToLower() == "anonymous";
        }
        /// <summary>Test validity formulara</summary>
        /// <returns>true / false</returns>
        protected bool CheckForm()
        {
            if (FText_Name.Text.Trim() == "") // Nazov spojenia
            {
                IApp.ShowError(lang.FORM_EDIT_CHANGE_FTP_CONNECTION_TEXT_12);
                FText_Name.Focus();
                return false;
            }

            if (FText_ServerName.Text.Trim() == "") // Nazov FTP servera
            {
                IApp.ShowError(lang.FORM_EDIT_CHANGE_FTP_CONNECTION_TEXT_13);
                FText_ServerName.Focus();
                return false;
            }

            return true;
        }
        /// <summary>Nacitanie obsahu formulara</summary>
        /// <param name="Result">vysledok</param>
        protected void ReadForm(IFTPStorageDef Result)
        {
            Result.Conn_Name = FText_Name.Text.Trim();
            Result.Conn_Caption = FText_Caption.Text.Trim();
            Result.Conn_ServerName = FText_ServerName.Text.Trim();
            Result.Conn_RemoteFolder = FText_RemoteFolder.Text.Trim();
            Result.Conn_PassiveMode = FCheck_Pasive.Checked;
            if (FCheck_AnonymousLogin.Checked)
            {
                Result.Conn_LoginName = "anonymous"; Result.Conn_LoginPwd = "";
            }
            else
            {
                Result.Conn_LoginName = FText_LoginName.Text.Trim();
                Result.Conn_LoginPwd = FText_LoginPwd.Text;
            }
        }

        /// <summary>Event: OnClick pre tlacidlo FBtn_OK</summary>
        /// <param name="pSender">zdroj</param>
        /// <param name="e">parameter</param>
        protected void Event_BtnOKClick(object pSender, EventArgs e)
        {
            if (!CheckForm()) return; // Otestujem obsah
            ReadForm(Value); // Nacitam obsah
            DialogResult = System.Windows.Forms.DialogResult.OK; // Ukoncim dialog
        }
    }

    /// <summary>Trieda IConnectionProvider</summary>
    public class IConnectionProvider : IDisposable
    {
        public int ID; // ID providera

        /// <summary>Konstruktor</summary>
        public IConnectionProvider()
        {
            ID = 0;
        }
        /// <summary>Dispose metoda</summary>
        public virtual void Dispose()
        {
        }
        /// <summary>Aktualizacia po zmene definicie zariadenia</summary>
        /// <param name="pValue">objekt</param>
        public virtual void UpdateStorageDefs(ICloudStorageDef pValue)
        {
        }

        /// <summary>Property: PasswordCacheID</summary>
        public virtual string PasswordCacheID
        {
            get
            {
                return "";
            }
        }
        /// <summary>Property: HotPathConnectionProvider</summary>
        public virtual string HotPathConnectionProvider
        {
            get
            {
                return "";
            }
        }
    }

    /// <summary>Trieda IFTPConnectionProvider</summary>
    public class IFTPConnectionProvider : IConnectionProvider
    {
        public const int ERROR_MAIN_SOCKET_CONNECT = -1;
        public const int ERROR_SERVER_CONNECT = -2;
        public const int ERROR_SERVER_BAD_LOGIN = -3;
        public const int ERROR_SERVER_ANSWER = -4;
        public const int ERROR_SERVER_SOCKET_ERROR = -5;
        public const int ERROR_SERVER_FILE_EXISTS = -6;
        public const int ERROR_SERVER_DIRECTORY_EXISTS = -7;
        public const int ERROR_SERVER_ACCESS_DENIED = -8;
        public const int ERROR_SERVER_DIR_NOT_EMPTY = -9;

        public const int RECONECT_TIMEOUT = 5 * 60;

        public IFTPStorageDef ConnDefs;
        public string LastError; // Posledna chyba
        public bool ServerTypeLinux; // Priznak linuxovskeho servra
        public string LastValidLogin, LastValidPwd; // Posledne platne meno a heslo

        protected System.Net.Sockets.Socket FMainSocket; // Hlavny socket
        protected System.Net.IPEndPoint FMainIPEndPoint;
        protected System.Net.Sockets.Socket FDataSocket; // Datovy riadiaci socket
        protected System.Net.Sockets.Socket FClientDataSocket; // Datova komunikacia servera
        protected int FTimeout; // Timeout
        protected System.Collections.Generic.List<string> FResponses; // Zoznam jednotlivych prijatych odpovedi
        protected DateTime FLastReadFromMainConnection; // Posledne citanie z hlavneho socketu

        /// <summary>Property: PasswordCacheID</summary>
        public override string PasswordCacheID
        {
            get
            {
                return ConnDefs.PasswordCacheID;
            }
        }
        /// <summary>Property: HotPathConnectionProvider</summary>
        public override string HotPathConnectionProvider
        {
            get
            {
                return ConnDefs.HotPathConnectionProvider;
            }
        }

        /// <summary>Konstruktor</summary>
        /// <param name="pDef">objekt s definiciou</param>
        public IFTPConnectionProvider(IFTPStorageDef pDef)
        {
            ID = pDef.ID; // Inicializacia
            FMainSocket = null;
            FMainIPEndPoint = null;
            FTimeout = 20000;
            FDataSocket = null;
            FResponses = new System.Collections.Generic.List<string>();
            ServerTypeLinux = true;
            
            ConnDefs = new IFTPStorageDef();
            ConnDefs.Assign(pDef); // Skopirujem data
        }
        /// <summary>Dispose metoda</summary>
        public override void Dispose()
        {
            CloseConnection();
            base.Dispose();
        }

        /// <summary>Vytvorenie klonu objektu</summary>
        /// <returns>novy objekt</returns>
        public IFTPConnectionProvider Clone()
        {
            IFTPConnectionProvider Result = new IFTPConnectionProvider(ConnDefs); // Vytvorim objekt
            Result.LastValidLogin = LastValidLogin; Result.LastValidPwd = LastValidPwd;
            return Result;
        }

        /// <summary>Aktualizacia po zmene definicie zariadenia</summary>
        /// <param name="pValue">objekt</param>
        public override void UpdateStorageDefs(ICloudStorageDef pValue)
        {
            CloseClientDataSocket(); // Uzatvorim vsetky spojenia
            CloseDataConnection();
            CloseConnection();
            ConnDefs.Assign((IFTPStorageDef)pValue); // Skopirujem nove hodnoty
            IApp.GlobalData.SavePassword(PasswordCacheID, null); // Vymazem heslo z cache
        }

        /// <summary>Nacitanie retazca zo socketu</summary>
        /// <param name="pSocket">socket</param>
        /// <param name="pSafeWait">priznak cakania na stream po nacitani dat</param>
        /// <returns>retazec alebo null</returns>
        protected string ReadString(System.Net.Sockets.Socket pSocket, bool pSafeWait = false)
        {
            try
            {
                System.Text.StringBuilder B = new System.Text.StringBuilder(); // Builder
                byte[] Buffer = new byte[32768]; // Buffer pre data

                System.Diagnostics.Stopwatch W = new System.Diagnostics.Stopwatch();
                W.Start();
                while (pSocket.Available < 1)
                {
                    System.Threading.Thread.Sleep(10);
                    if (!ServerTypeLinux) // Windows?
                    {
                        if (pSocket.Available == 0) System.Threading.Thread.Sleep(500); // Pockam este pol sekundy pre istotu
                        break;
                    }
                    if (W.ElapsedMilliseconds > FTimeout)
                    {
                        return null;
                    }
                }
                while (pSocket.Available > 0) // Pokial su data dostupne...
                {
                    int C = pSocket.Receive(Buffer, 32768, 0);
                    B.Append(System.Text.Encoding.Default.GetString(Buffer, 0, C));
                    if (pSafeWait) System.Threading.Thread.Sleep(200); // Pockam pre istotu
                }

                return B.ToString();
            }
            catch (Exception E) // Chyba
            {
                LastError = E.Message;
                return null;
            }
        }
        /// <summary>Zaslanie retazca</summary>
        /// <param name="pSocket">socket</param>
        /// <param name="pValue">retazec</param>
        /// <returns>true / false</returns>
        protected bool SendString(System.Net.Sockets.Socket pSocket, string pValue)
        {
            try
            {
                while (true)
                {
                    if (pSocket.Available != 0)
                    {
                        byte[] BB = new byte[pSocket.Available];
                        pSocket.Receive(BB);
                    }
                    else break;
                }
                byte[] Data = System.Text.Encoding.Default.GetBytes(pValue); // Prevediem na data
                pSocket.Send(Data, Data.Length, 0);
                return true; // Vsetko OK
            }
            catch (Exception E) // Chyba
            {
                LastError = E.Message;
                return false;
            }
        }
        /// <summary>Nacitanie odpoved servra</summary>
        /// <returns>odpoved alebo null</returns>
        protected string ReadResponse()
        {
            if (FResponses.Count == 0)
            {
                string S = ReadString(FMainSocket); // Nacitam odpoved zo servera
                FLastReadFromMainConnection = DateTime.Now; // Cas citania
                if (S == null) return null;
                string[] S1 = S.Split(new string[] { System.Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
                foreach (string S2 in S1)
                {
                    if (S2.StartsWith("000")) continue;
                    FResponses.Add(S2);
                }
            }
            if (FResponses.Count == 0) return null; // Chyba prijatia response?

            string Result = FResponses[0];
            FResponses.RemoveAt(0);
            return Result; // Vratim prvu odpoved v zozname
        }

        /// <summary>Otvorenie datoveho spojenia</summary>
        /// <returns>0 alebo kod chyby</returns>
        protected int OpenDataConnection()
        {
            try
            {
                string Response = null;

                CloseDataConnection(); // Uzatvorim existujuce
                if (ConnDefs.Conn_PassiveMode) // Pasivny mod?
                {
                    SendString(FMainSocket, "PASV\r\n"); // Poslem prikaz
                    Response = ReadResponse(); // Nacitam vysledok

                    if ((Response == null) || (!Response.StartsWith("227"))) return ERROR_SERVER_SOCKET_ERROR;

                    int I1 = Response.IndexOf('(');
                    int I2 = Response.IndexOf(')', I1);
                    string[] RR = Response.Substring(I1 + 1, I2 - I1 - 1).Split(new char[] { ',' }); // Rozdelim dosle data

                    if (RR.Length != 6) return ERROR_SERVER_SOCKET_ERROR;

                    // Vytvorim spojenie
                    FClientDataSocket = new System.Net.Sockets.Socket(System.Net.Sockets.AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp);
                    System.Net.IPEndPoint EP = new System.Net.IPEndPoint(System.Net.Dns.GetHostByName(String.Format("{0}.{1}.{2}.{3}", RR[0], RR[1], RR[2], RR[3])).AddressList[0], (ICore.ToInt(RR[4]) << 8) + ICore.ToInt(RR[5]));
                    FClientDataSocket.Connect(EP);

                    return 0; // OK
                }

                FDataSocket = new System.Net.Sockets.Socket(System.Net.Sockets.AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp);
                FDataSocket.SendTimeout = FTimeout; FDataSocket.ReceiveTimeout = FTimeout;
                string S = FMainSocket.LocalEndPoint.ToString(); // Ziskam IP adresu hlavneho socketu
                int I = S.IndexOf(':');
                if (I == -1) return ERROR_SERVER_CONNECT;
                string IP = S.Substring(0, I);

                System.Net.IPEndPoint LEP = new System.Net.IPEndPoint(System.Net.IPAddress.Parse(IP), 0); // Automaticky port
                FDataSocket.Bind(LEP); // Bind na lokalnu adresu

                S = FDataSocket.LocalEndPoint.ToString(); // Ziskam IP adresu datoveho socketu
                I = S.IndexOf(':');
                if (I == -1) return ERROR_SERVER_CONNECT;
                int Port = ICore.ToInt(S.Substring(I + 1));
                if (Port == 0) return ERROR_SERVER_CONNECT;

                FDataSocket.Listen(1); // Mod listen

                S = string.Format("PORT {0},{1},{2}", IP.Replace('.', ','), Port / 256, Port % 256); // Command PORT
                SendString(FMainSocket, S + "\r\n"); // Poslem PORT prikaz
                Response = ReadResponse(); // Ziskam odpoved
                if ((Response == null) || (!Response.StartsWith("200"))) return ERROR_SERVER_CONNECT;

                return 0; // Vsetko OK
            }
            catch(Exception E)
            {
                LastError = E.Message;
                return ERROR_SERVER_CONNECT;
            }
        }
        /// <summary>Uzatvorenie datoveho spojenia</summary>
        protected void CloseDataConnection()
        {
            try
            {
                if (FDataSocket != null)
                {
                    FDataSocket.Close();
                    FDataSocket = (System.Net.Sockets.Socket)ICore.DisposeObject(FDataSocket);
                }
            }
            catch
            {
            }
        }
        /// <summary>Otvorenie serverovej odpovede</summary>
        /// <returns>0 alebo kod chyby</returns>
        protected int OpenClientDataSocket()
        {
            try
            {
                if (ConnDefs.Conn_PassiveMode) // Pasivny mod?
                {
                    return 0;
                }
                else // Aktivny mod
                {
                    FClientDataSocket = FDataSocket.Accept();
                    if (FClientDataSocket == null) return ERROR_SERVER_SOCKET_ERROR;
                    FClientDataSocket.SendTimeout = FTimeout; FClientDataSocket.ReceiveTimeout = FTimeout;
                    return 0; // OK
                }
            }
            catch(Exception E)
            {
                LastError = E.Message;
                return ERROR_SERVER_SOCKET_ERROR;
            }
        }
        /// <summary>Uzatvorenie serverovej odpovede</summary>
        protected void CloseClientDataSocket()
        {
            try
            {
                if (FClientDataSocket != null)
                {
                    FClientDataSocket.Close();
                    FClientDataSocket = (System.Net.Sockets.Socket)ICore.DisposeObject(FClientDataSocket);
                }
            }
            catch
            {
            }
        }

        /// <summary>Vytvorenie spojenia s FTP serverom</summary>
        /// <returns>0 alebo kod chyby</returns>
        public int OpenConnect(string pName = null, string pPwd = null)
        {
            try
            {
                if ((FMainSocket != null) && (FMainSocket.Connected))
                {
                    if ((DateTime.Now - FLastReadFromMainConnection).TotalSeconds <= RECONECT_TIMEOUT) return 0; // Som uz pripojeny?
                }
                CloseConnection(); // Uzatvorim aktualne spojenie

                FMainSocket = new System.Net.Sockets.Socket(System.Net.Sockets.AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp);
                FMainSocket.SendTimeout = FTimeout; FMainSocket.ReceiveTimeout = FTimeout;
                FMainIPEndPoint = new System.Net.IPEndPoint(System.Net.Dns.GetHostByName(ConnDefs.Conn_ServerName).AddressList[0], 21);

                try
                {
                    FMainSocket.Connect(FMainIPEndPoint); // Otvorim spojenie
                }
                catch (Exception E) // Chyba?
                {
                    LastError = E.Message;
                    FMainIPEndPoint = null;
                    FMainSocket = (System.Net.Sockets.Socket)ICore.DisposeObject(FMainSocket);
                    return ERROR_MAIN_SOCKET_CONNECT;
                }

                string Response = ReadResponse(); // Ziskam uvodnu spravu
                if ((Response == null) || (!Response.StartsWith("220")))
                {
                    return ERROR_SERVER_CONNECT;
                }

                SendString(FMainSocket, "SYST\r\n"); // Zaslem prikaz na zadanie mena
                Response = ReadResponse(); // Ziskam odpoved
                if (Response != null) ServerTypeLinux = Response.ToLower().Contains("unix");

                string Name = pName == null ? ConnDefs.Conn_LoginName : pName; // Urcim login a heslo
                string Pwd = pPwd == null ? ConnDefs.Conn_LoginPwd : pPwd;

                SendString(FMainSocket, "USER " + Name + "\r\n"); // Zaslem prikaz na zadanie mena
                Response = ReadResponse(); // Ziskam odpoved

                if ((Response == null) || (Response.Length < 3))
                {
                    return ERROR_SERVER_CONNECT;
                }

                switch (Response.Substring(0, 3))
                {
                    case "331": // Zaslanie hesla
                        {
                            SendString(FMainSocket, "PASS " + Pwd + "\r\n"); // Zaslem heslo
                            Response = ReadResponse(); // Ziskam odpoved
                            if ((Response == null) || (!Response.StartsWith("230"))) return ERROR_SERVER_BAD_LOGIN;
                        } break;
                    case "230": break;
                    default: // Nezname?
                        {
                            return ERROR_SERVER_CONNECT;
                        }
                }

                SendString(FMainSocket, "TYPE I\r\n"); // Poslem binarny mod
                Response = ReadResponse();
                if ((Response == null) || (!Response.StartsWith("200"))) return ERROR_SERVER_ANSWER;

                return 0; // Vsetko OK
            }
            catch (Exception E)
            {
                LastError = E.Message;
                return ERROR_MAIN_SOCKET_CONNECT;
            }
        }
        /// <summary>Uzatvorenie spojenia</summary>
        public void CloseConnection()
        {
            try
            {
                CloseDataConnection(); // Uzatvorim datove spojenie
                if (FMainSocket != null)
                {
                    SendString(FMainSocket, "QUIT\r\n");
                    FMainSocket = (System.Net.Sockets.Socket)ICore.DisposeObject(FMainSocket);
                }
                FMainIPEndPoint = null;
                FResponses.Clear();
            }
            catch
            {
            }
        }

        /// <summary>Inicializacia nacitania obsahu priecinku</summary>
        /// <param name="pPath">cesta pre expanziu</param>
        /// <param name="pReader">objekt pre ulozenie vysledku</param>
        /// <returns>0 alebo kod chyby</returns>
        public int BeginList(string pPath, out System.IO.StringReader ResultReader)
        {
            ResultReader = null; // Inicializacia vysledku
            try
            {
                //CloseClientDataSocket(); CloseDataConnection();

                string Response;

                int R = OpenDataConnection(); // Otvorim datove spojenie
                if (R != 0) return R;

                SendString(FMainSocket, "LIST /" + pPath + "\r\n"); // Poslem prikaz
                Response = ReadResponse(); // Nacitam odpoved

                if (Response == null)
                {
                    CloseDataConnection(); // Uzatvorim datove spojenie
                    return ERROR_SERVER_ANSWER; // Chybna odpoved
                }
                if (!((Response.StartsWith("125")) || Response.StartsWith("150")))
                {
                    CloseDataConnection(); // Uzatvorim datove spojenie
                    return ERROR_SERVER_ANSWER; // Chyba?
                }

                R = OpenClientDataSocket();
                if (R != 0) return R; // Chyba?

                Response = ReadString(FClientDataSocket, true); // Ziskam data s polozkami
                if (!string.IsNullOrEmpty(Response)) ServerTypeLinux = (Response[0] == 'd') || (Response[0] == '-'); // Urcim typ servera

                ResultReader = new System.IO.StringReader(Response == null ? "" : Response); // Vytvorim reader
                if (ServerTypeLinux) // Ak je linux tak odstranim prve dva zaznamy - . a ..
                {
                    ResultReader.ReadLine(); ResultReader.ReadLine();
                }
                return 0;
            }
            catch(Exception E)
            {
                LastError = E.Message;
                return ERROR_SERVER_ANSWER;
            }
        }
        /// <summary>Uzatvorenie nacitania obsahu</summary>
        public int EndList()
        {
            try
            {
                if (FMainSocket == null) return 0; // Ziadne spojenie?
                CloseClientDataSocket();
                CloseDataConnection(); // Uzatvorim datove spojenie

                System.Threading.Thread.Sleep(50);
                if (FMainSocket.Available > 0)
                {
                    string Response = ReadResponse(); // Nacitam odpoved
                    if ((Response == null) || (!Response.StartsWith("226"))) return ERROR_SERVER_ANSWER;
                }
                FResponses.Clear();
                return 0; // Vsetko OK
            }
            catch (Exception E)
            {
                LastError = E.Message;
                return ERROR_SERVER_ANSWER;
            }
        }
        /// <summary>Ziskanie dalsieho zaznamu list</summary>
        /// <returns>zaznam alebo null</returns>
        public string NextListItem(System.IO.StringReader pReader)
        {
            try
            {
                return pReader.ReadLine();
            }
            catch
            {
                return null;
            }
        }

        /// <summary>Otvorenie suboru na stiahnutie</summary>
        /// <param name="pFullName">nazov</param>
        /// <returns>0 alebo kod chyby</returns>
        public int OpenFileForDownload(string pFullName)
        {
            FResponses.Clear();
            int R = OpenDataConnection(); // Otvorim datovy socket
            if (R != 0) return R;

            string Response;
            SendString(FMainSocket, "RETR /" + pFullName + "\r\n"); // Poslem ziadost o subor
            Response = ReadResponse();
            if (Response == null) return ERROR_SERVER_ANSWER;


            switch (Response.Substring(0, 3))
            {
                case "125":
                case "150":
                    break;
                default: return ERROR_SERVER_ANSWER;
            }

            R = OpenClientDataSocket(); // Otvorim socket pre prijem dat
            return R; 
        }
        /// <summary>Uzatvorenie suboru pre stahovanie</summary>
        /// <returns>0 alebo kod chyby</returns>
        public int CloseFileForDownload()
        {
            CloseClientDataSocket(); // Uzatvorim socket
            CloseDataConnection();

            string Response = ReadResponse();
            if (Response == null) return ERROR_SERVER_ANSWER;
            switch (Response.Substring(0, 3))
            {
                case "226":
                case "250":
                    break;
                default: return ERROR_SERVER_ANSWER;
            }
            return 0; // Vsetko OK
        }
        /// <summary>Stiahnutie dat suboru</summary>
        /// <param name="pBuffer">buffer</param>
        /// <param name="pMaxLength">max. velkost</param>
        /// <returns>pocet stiahnutych dat</returns>
        public int DownloadFileData(byte[] pBuffer, int pMaxLength, int pStart = 0)
        {
            try
            {
                if (FClientDataSocket.Available < 0) System.Threading.Thread.Sleep(50); // Pockam ak treba
                return FClientDataSocket.Receive(pBuffer, pStart, pMaxLength, 0); // Citam data
            }
            catch
            {
                return -1;
            }
        }

        /// <summary>Vytvorenie suboru na ulozenie</summary>
        /// <param name="pFullName">nazov</param>
        /// <returns>0 alebo kod chyby</returns>
        public int CreateFileForUpload(string pFullName, bool pCanOverwrite = true)
        {
            FResponses.Clear();
            int R = OpenDataConnection(); // Otvorim datovy socket
            if (R != 0) return R;

            string Response;

            if (!pCanOverwrite)
            {
                SendString(FMainSocket, "SIZE /" + pFullName + "\r\n");
                Response = ReadResponse();
                if ((Response == null) || (Response.StartsWith("213"))) return ERROR_SERVER_FILE_EXISTS;
            }

            SendString(FMainSocket, "STOR /" + pFullName + "\r\n"); // Poslem ziadost o subor
            Response = ReadResponse();
            if (Response == null) return ERROR_SERVER_ANSWER;


            switch (Response.Substring(0, 3))
            {
                case "125":
                case "150":
                    break;
                case "550":
                    return ERROR_SERVER_ACCESS_DENIED;
                default: return ERROR_SERVER_ANSWER;
            }

            R = OpenClientDataSocket(); // Otvorim socket pre prijem dat
            return R;
        }
        /// <summary>Uzatvorenie suboru pre ulozenie</summary>
        /// <returns>0 alebo kod chyby</returns>
        public int CloseFileForUpload()
        {
            CloseClientDataSocket(); // Uzatvorim socket
            CloseDataConnection();

            string Response = ReadResponse();
            if (Response == null) return ERROR_SERVER_ANSWER;
            switch (Response.Substring(0, 3))
            {
                case "226":
                case "250":
                    break;
                default: return ERROR_SERVER_ANSWER;
            }
            return 0; // Vsetko OK
        }
        /// <summary>Zaslanie dat suboru</summary>
        /// <param name="pBuffer">buffer</param>
        /// <param name="pMaxLength">velkost</param>
        /// <returns>pocet stiahnutych dat</returns>
        public int UploadFileData(byte[] pBuffer, int pLength, int pStart = 0)
        {
            try
            {
                return FClientDataSocket.Send(pBuffer, pStart, pLength, 0); // Zapisem data
            }
            catch
            {
                return -1;
            }
        }

        /// <summary>Ziskanie dlzky suboru</summary>
        /// <param name="pFullName">nazov</param>
        /// <returns>dlzka</returns>
        public long GetFileSize(string pFullName) 
        {
            SendString(FMainSocket, "SIZE " + pFullName + "\r\n"); // Poslem prikaz
            string Response = ReadResponse(); // Nacitam vysledok
            if ((Response == null) || (!Response.StartsWith("213"))) return -1;
            return ICore.ToLong(Response.Substring(4).Trim()); // Vratim dlzku
        }
        /// <summary>Vytvorenie priecinku</summary>
        /// <param name="pFullName">nazov</param>
        /// <returns>0 alebo kod chyby</returns>
        public int CreateFolder(string pFullName)
        {
            string Response;

            SendString(FMainSocket, "MKD /" + pFullName + "\r\n");
            Response = ReadResponse(); // Nacitam odpoved

            if (Response == null) return ERROR_SERVER_ANSWER;
            switch (Response.Substring(0, 3))
            {
                case "257": return 0;
                case "550":
                    {
                        SendString(FMainSocket, "CWD /" + pFullName + "\r\n"); // Test na existenciu priecinku
                        Response = ReadResponse(); // Nacitam odpoved
                        if ((Response == null) || (!Response.StartsWith("250"))) return ERROR_SERVER_FILE_EXISTS;
                        return ERROR_SERVER_DIRECTORY_EXISTS;
                    }
                default: return ERROR_SERVER_ANSWER;
            }
        }
        /// <summary>Premenovanie objektu</summary>
        /// <param name="pOldName">stare meno</param>
        /// <param name="pNewName">nove meno</param>
        /// <returns>0 alebo kod chyby</returns>
        public int RenameItem(string pOldName, string pNewName)
        {
            string Response;

            SendString(FMainSocket, "RNFR /" + pOldName + "\r\n");
            Response = ReadResponse(); // Nacitam odpoved
            if (Response == null) return ERROR_SERVER_ANSWER;
            switch (Response.Substring(0, 3))
            {
                case "350": break;
                default: return ERROR_SERVER_ANSWER;
            }

            int I = pOldName.LastIndexOf('/'); // Vytvorim plne nove meno
            string FN = I == -1 ? pNewName : pOldName.Substring(0, I + 1) + pNewName;

            SendString(FMainSocket, "RNTO " + FN + "\r\n");
            Response = ReadResponse(); // Nacitam odpoved
            if (Response == null) return ERROR_SERVER_ANSWER;
            switch (Response.Substring(0, 3))
            {
                case "250": break;
                case "550": return ERROR_SERVER_DIRECTORY_EXISTS;
                default: return ERROR_SERVER_ANSWER;
            }

            return 0;
        }
        /// <summary>Vymazanie suboru</summary>
        /// <param name="pName">nazov</param>
        /// <returns>0 alebo kod chyby</returns>
        public int DeleteFile(string pName)
        {
            SendString(FMainSocket, "DELE /" + pName + "\r\n");
            string Response = ReadResponse(); // Nacitam odpoved
            if (Response == null) return ERROR_SERVER_ANSWER;
            switch (Response.Substring(0, 3))
            {
                case "250": return 0;
                default: return ERROR_SERVER_ANSWER;
            }
        }
        /// <summary>Vymazanie priecinku</summary>
        /// <param name="pName">nazov</param>
        /// <returns>0 alebo kod chyby</returns>
        public int DeleteFolder(string pName)
        {
            SendString(FMainSocket, "RMD /" + pName + "\r\n");
            string Response = ReadResponse(); // Nacitam odpoved
            if (Response == null) return ERROR_SERVER_ANSWER;
            switch (Response.Substring(0, 3))
            {
                case "250": return 0;
                case "550": return ERROR_SERVER_DIR_NOT_EMPTY;
                default: return ERROR_SERVER_ANSWER;
            }
        }

        /// <summary>Prerusenie suborovej operacie</summary>
        /// <returns>0 alebo kod chyby</returns>
        public int AbortFileOperation()
        {
            SendString(FMainSocket, "ABOR\r\n");
            string Response = ReadResponse(); // Nacitam odpoved
            if (Response == null) return ERROR_SERVER_ANSWER;
            return 0;
        }
        /// <summary>Ziskanie typu objektu</summary>
        /// <param name="pName">nazov</param>
        /// <returns>0 - priecinok, 1 - subor, -1 neexistuje</returns>
        public int GetObjectType(string pName)
        {
            SendString(FMainSocket, "SIZE /" + pName + "\r\n");
            string Response = ReadResponse(); // Nacitam odpoved
            if (Response == null) return -1;
            switch (Response.Substring(0, 3))
            {
                case "213": return 1;
                case "550": return 0;
                default: return -1;
            }
        }
        /// <summary>Nastavenie atributov</summary>
        /// <param name="pName">nazov</param>
        /// <param name="pValue">hodnota</param>
        /// <returns>0 alebo kod chyby</returns>
        public int SetAttributes(string pName, uint pValue)
        {
            SendString(FMainSocket, string.Format("SITE CHMOD {0} /{1}\r\n", Convert.ToString(pValue, 8), pName));
            string Response = ReadResponse(); // Nacitam odpoved
            if (Response == null) return -1;

            switch (Response.Substring(0, 3))
            {
                case "200": return 0;
                default: return -1;
            }
        }
    }
}
